function JGrid(owner, parent, name, dataset, columns, widths, rect, flags, tvFields, tab)
{
	// private properties
	var self = this,
		_disposed = false,
		_form,
		_savedRec = null,
		_scrollTop  = null,
		_scrollLeft = null,
		_resizing = {isResizing : false, elem : null, pos : null, width : 0, totalWidth : 0, editWidth : 0 },
		_widths = {widths: [], totalWidth: 0, mainWidth: 0, colWidth: 0, fixedWidth: false},
		_loadCnt = 0,
		_rendered = false,
		_isRightMost = true,
		_isBottomMost = true,
		_ds,
		_selector,
		_fieldMenu,
		_fieldMenuField = null;

	this.initialize(owner, parent, name, null, null, rect, flags, null, dataset, tab);

	// public properties
	this.columns = columns.split(scItemSep);
	this.widths = widths.split(scItemSep);
	this.tvFields = tvFields.split(scItemSep);
	this.hasTree = false;
	this.methods = (cmRender | cmLoad | cmSave);
	this.selectedRows = [];
	this.activeRow = null;
	this.editRow = null;
	this.edits = [];
	this.multiSelect = true;
	this.pageno = null;
	this.pagingHelper = new FastString();

	this.msgPage = this.owner.localize("msgPage");
	if (this.msgPage == null)
		this.msgPage = "Strana";
	this.msgGoto = this.owner.localize("msgGoto");
	if (this.msgGoto == null)
		this.msgGoto = "Posun na:";
	this.msgGo   = this.owner.localize("msgGo");
	if (this.msgGo == null)
		this.msgGo = "Provést posun";

	// public methods
	this.click        = click;
	this.dispose      = dispose;
	this.highlight    = highlight;
	this.isComputedColumn = isComputedColumn; 
	this.load         = load;
	this.navigate     = navigate; 
	this.mark         = mark;

	this.onScroll     = onScroll;
	this.onKeyDown	  = _keyDown;
	this.render       = render;
	this.selectRow    = selectRow;
	this.setActiveRow	= setActiveRow;
	this.toggle       = toggle;

	// initialization

	this.owner.addImage("field-arrow",     "field-arrow.gif",     "controls");
	this.owner.addImage("grid-first",      "grid-first.gif",      "controls");
	this.owner.addImage("grid-prior",      "grid-prior.gif",      "controls");
	this.owner.addImage("grid-next",       "grid-next.gif",       "controls");
	this.owner.addImage("grid-last",       "grid-last.gif",       "controls");
	this.owner.addImage("grid-row-edit",   "grid-row-edit.gif",   "controls");
	this.owner.addImage("grid-row-insert", "grid-row-insert.gif", "controls");
	this.owner.addImage("grid-row-delete", "grid-row-delete.gif", "controls");
	this.owner.addImage("grid-row-insert-level", "grid-row-insert2.gif", "controls");

	_ds = this.dataset;
	_selector = _ds.recordSelector;

	_form = this.getForm();
	if ((_form != null) && (_form.activeControl == null))
		_form.setActiveControl(this);

  // implementation

	function render(fs)
	{
		if (_rendered)
			return;

		fs.append('<div class="grid" id="' + self.name + '">');
		fs.append('<div class="gridHeaderOut" id="' + self.name + '_headerOut">');
		fs.append('  <div class="gridHeaderIn" id="' + self.name + '_headerIn"></div>');
		fs.append('</div>');
		fs.append('<div class="gridBodyOut" id="' + self.name + '_bodyOut">');
		fs.append('  <div class="gridBodyIn" id="' + self.name + '_bodyIn"></div>');
		fs.append('</div>');

		fs.append('<div class="gridNav" id="' + self.name + '_nav">')
		fs.append('<nobr>');

		fs.append('<img id="fp" hspace="3" src="' + self.owner.images['grid-first'].src + '" style="cursor: hand; cursor: pointer;">');
		fs.append('<img id="pp" hspace="3" src="' + self.owner.images['grid-prior'].src + '" style="cursor: hand; cursor: pointer;">');

		if (!self.dataset.isReadOnly())
		{
			fs.append('<img id="' + self.name + '_ir" hspace="3" src="' + self.owner.images['grid-row-insert'].src + '" style="cursor: hand; cursor: pointer;">');
//			if (self.hasTree)
			fs.append('<img id="' + self.name + '_irl" hspace="3" src="' + self.owner.images['grid-row-insert-level'].src  + '" style="cursor: hand; cursor: pointer; display: none;">');
		
			fs.append('<img id="er" hspace="3" src="' + self.owner.images['grid-row-edit'].src + '" style="cursor: hand; cursor: pointer;">');
			fs.append('<img id="dr" hspace="3" src="' + self.owner.images['grid-row-delete'].src + '" style="cursor: hand; cursor: pointer;">');
		}
		fs.append('<img id="np" hspace="3" src="' + self.owner.images['grid-next'].src + '" style="cursor: hand; cursor: pointer;">');
		fs.append('<img id="lp" hspace="3" src="'  + self.owner.images['grid-last'].src + '" style="cursor: hand; cursor: pointer;">');
		fs.append('</nobr>');
		fs.append('<span id="' + self.name + '_go" style="position: relative; white-space: nowrap;"></span>');
		fs.append('</div>');

		fs.append('</div>');

		_fieldMenu = new JContextMenu(self.owner, self.name, self.name + '_FieldMenu');
		_fieldMenu.showBelow = true;
		_fieldMenu.addMenuItem(self.owner, self.name + '_FieldMenu', self.name + '_FieldMenu_QuickFilter', self.owner.localize("dlgQuickFilter"));
		_fieldMenu.render(fs);

		_rendered = true;
	}

	function load()
	{
		var i;
		var grid = self.$$();
		var style = grid.style;
		var hdrOut = self.$$('_headerOut');
		var hdrIn = self.$$('_headerIn');
		var bodyOut = self.$$('_bodyOut');
		var bodyIn = self.$$('_bodyIn');

		var ds = self.dataset;
		var recs = ds.records;
		var recCount = ds.getRecCount();
		var cols = self.columns;
		var magicConst = (browser.isIE) ? 0 : 25;
		var fs = new FastString();

		if (_loadCnt == 0)
		{
			if (ds.getRecNo() == null)
				ds.gotoRow(0);

			var pos = self.checkPosition();
			_isRightMost = pos.isRightMost;
			_isBottomMost = pos.isBottomMost;

			style.top = self.rect.top;
			style.left = self.rect.left;

			if (_isRightMost)
			{
				var pw = grid.parentNode.clientWidth;
				if (pw == 0)
					pw = self.getParentObject().$$().clientWidth;

				var divider = 1;
				if (self.getParentObject().name.startsWith('F574018_10')) /* Obecne prilohy*/
					divider = 2;
				style.width = (_widths.mainWidth = pw - self.rect.left - 8) / divider;
			}
			else
			{
				style.position = 'absolute';
				style.width = _widths.mainWidth = self.rect.width;
			}
			self.$$('_nav').style.width = style.width;
			
			if (_isBottomMost)
			{
				var ph = grid.parentNode.clientHeight;
				if (ph == 0)
					ph = self.getParentObject().$$().clientHeight;
				var newH = ph - self.rect.top - 20;
				style.height = newH;
			}
			else
			{
				style.height = self.rect.height - 5;
				style.position = 'absolute';
			}
			bodyOut.style.height = Math.max(grid.clientHeight - Math.max(self.$$('_nav').offsetHeight, 20) - Math.max(hdrOut.offsetHeight, 20) - 24, 40);

			var dividerWidth = 9;

			for (i = 0; i < self.widths.length; i++)
			{
			  _widths.widths[i] = ((self.widths[i] == "") ? grdDefaultWidth : parseInt(self.widths[i]));
			  _widths.totalWidth += (_widths.widths[i] + dividerWidth);
			}

			//if (browser.isGeckoBased || browser.isIE)
			//  _widths.totalWidth += (self.widths.length * 3);
			  
			_widths.totalWidth += (self.widths.length * 4);

			_widths.colWidth = Math.floor((_widths.mainWidth - (dividerWidth * cols.length) - ((browser.isIE) ? 50 : 10) - magicConst) / cols.length);
			_widths.fixedWidth = (_widths.mainWidth > _widths.totalWidth);

			if (!_widths.fixedWidth)
			{
			  hdrIn.style.width = bodyIn.style.width = _widths.totalWidth + 15 + magicConst;
			}
			else
			{
			  hdrIn.style.width = bodyIn.style.width = _widths.mainWidth - 10;
			}

			var colLen = cols.length;
			for (i = 0; i < colLen; i++)
			{
				var caption = cols[i];
				if (caption == '__Tree')
				{
					self.hasTree = true;
					caption = "";
					var irl = self.$$('_irl');
					if (irl)
						irl.style.display = '';
				}
				else
				{
				  if (ds.fields[caption] != null)
				    caption = ds.fields[caption].caption;
				}
	
				if (caption == '')
				  caption = '&nbsp;';
	
				var width = (_widths.fixedWidth) ? _widths.colWidth : _widths.widths[i];
//				fs.append('<div id="' + self.name + '_headerCell_' + i + '" _col="' + i + '" class="gridHeaderCell" style="width: ' + width + ';">' + caption + '</div><div id="' + self.name + '_div_' + i + '" class="gridHeaderDivider">|</div>')
				fs.append('<div id="' + self.name + '_headerCell_' + i + '" _col="' + i + '" class="gridHeaderCell" style="width: ' + width + ';" title="' + caption + '"><img id="' + self.name + '_fieldArrow_' + i + '" _col="' + i + '" src="' + self.owner.images['field-arrow'].src + '" style="border: 0; cursor: hand; cursor: pointer; padding-right: 2;" />' + caption + '</div><div id="' + self.name + '_div_' + i + '" class="gridHeaderDivider">|</div>')
			}
	
			hdrIn.innerHTML = fs.toString();
			fs.clear();

			_fieldMenu.load();
			_fieldMenu.menuItemClick = _fieldMenuClick;
		}
		else
		{
			_removeInlineEdits();
		}

		var _t1 = new Date();

		i = recCount;
		if (recCount > 0)
		{
			do
			{
			  _renderRow(fs, recCount - i, cols);
			}
			while (--i);
		}

		var _t2 = new Date();
		self.owner._debugAdd('_renderRow(s) took: ' + (_t2 - _t1).toString() + '\n');
		self.owner._debugShow(true);

		bodyIn.innerHTML = fs.toString();

		if (self.$$('_ir') != null)
			self.$$('_ir').style.display = (utils.inSet(dfNoInsert, self.dataset.flags)) ? 'none' : '';

		if (_loadCnt == 0)
		{
			_renderPageSelector();
	
			//    grid.onscroll = self.onScroll;
			bodyOut.onscroll = function() { document.getElementById(self.name + '_headerIn').style.left = -this.scrollLeft; };

			grid.onmousedown = _mouseDown;
			grid.onmouseover = _mouseOver;
			grid.onmouseout  = _mouseOut;
			grid.onclick     = _mouseClick;
			grid.ondblclick  = _choose;

			grid.onkeydown = _keyDown;

			self.$(self.name + '_nav').onclick = _navIconClick;

			ds.oninfochanged.attach(_dsInfoChaged);
			ds.oncleared.attach(_recordsCleared);
			ds.onrecordadded.attach(_recordAdded);
			ds.onrecordchanged.attach(_recordChanged);
			ds.onrecorddeleted.attach(_recordDeleted);
			ds.onpositionchanged.attach(_positionChanged);

			if (self.getParentObject().onresize != null)
				self.getParentObject().onresize.attach(_setSize);
			else
				self.getForm().onresize.attach(_setSize);
		}

		_selector.clear();
		if (ds.getRecCount() > 0)
			self.setActiveRow(ds.getRecNo());

		_loadCnt++;
}

	function _renderRow(fs, i, cols)
	{
		var ii, j, _t3, ct, col, content, width, colType, padding, id, align, colClassName, add,
			_t1 = new Date(),
			colLen = cols.length,
			ds = self.dataset,
			fields = ds.fields,
			istr = i.toString(),
		    rec = ds.getRecord(i);

		self.owner._debugAdd('_renderRow(' + istr);

		var className = 'gridRow';
		if (i % 2)
		  className += ' gridRowOdd';

		fs.append('<div class="' + className + ' + " id="' + self.name + '_row_' + istr + '" _row="' + istr + '">');

		ii = colLen;
		if (colLen > 0)
		{
			do
			{
				j = colLen - ii;
				col = cols[j];
				_t3 = new Date();
	
				ct = _getColumnText(col, i);
				content = ct.content;
	
				width = ((_widths.fixedWidth) ? _widths.colWidth : _widths.widths[j]).toString();
				colType = (col == '__Tree') ? ftString : fields[col].type;
	
				padding = '';
				if (ct.padding > 0)
				{
					padding = 'padding-left:' + ct.padding;
					if (!browser.isIE)
						width = parseInt(width) - ct.padding;
				}
	
				id = (self.name + '_cell_' + istr + '_' + j.toString());
				align = ((colType == ftInteger) || (colType == ftFloat)) ? 'right' : 'left';
				colClassName = 'gridCell';
				add = '';

				if (((ds.isReadOnly(col, i)) || (rec.isNoData(col))) && (!ds.isReadOnly()))
				{
					colClassName += ' readOnly';
					add += ' _readOnly="true"';
				}
				else
				if (rec.isRequired(col))
				{
					colClassName += ' requiredField';
					add += ' _required="true"';
				}

				fs.append('<div id="' + id + '" class="' + colClassName + '" style="width: ' + width + '; text-align:' + align + ';' + padding + ';"' + add + '>' + content + '</div><div class="gridRowDivider">|</div>');
	
	//			self.owner._debugAdd(',' + ((new Date()) - _t3).toString());
			}
			while (--ii);
		}

		fs.append('</div>');

		self.owner._debugAdd(') took: ' + (new Date() - _t1).toString() + '\n');
	}

	function _renderPageSelector()
	{
		if (self.dataset.fields.length == 0)
			return;

		var pageCount = Math.floor(_ds.getRealRecCount() / _ds.pageSize);
		if (isNaN(pageCount))
			pageCount = 0;
		var currentPage = parseInt(_ds.pageNo) + 1;

		var items = '';
		for (var pages = 1; pages <= pageCount+1; pages++)
		{
			items += (pages + '=' + pages);
			if (pages < pageCount+1)
				items += scItemSep;
		}

		var fld = _ds.fields['__pgsel'];
		if (fld != null)
			fld.setItems(items);
		else
			_ds.addField(new JField('__pgsel', '', ftEnum, items, ffLocalField | ffNoRecStatusChange | ffHidden));

		var pageSel = new JEnumPicker(self.owner, self.parent, 'epk_pgsel_' + self.name, _ds, '__pgsel', ';;30;18', self.tab);
		self.owner.registerObject(pageSel.name, pageSel);
		self.addControl(pageSel);

		var nav = self.$(self.name + '_nav');

		var go = self.$(self.name + '_go');
		go.innerHTML = '<nobr>' + self.msgPage + " " + currentPage + "/" + (pageCount+1) + ', <a id="ps" href="javascript:;" title="' + self.msgGo + '">' + self.msgGoto + '</a>  &nbsp;' + pageSel.render() + '</nobr>';

		pageSel.load();
		pageSel.select(currentPage)

		self.$('epk_pgsel_' + self.name).className += ' gridPageSel';

		nav.style.display = 'block';
	}

	function _setSize()
	{
		var grid = self.$$();
		var style = grid.style;
		var hdrOut = self.$(self.name+ '_headerOut');
		var bodyOut = self.$(self.name+ '_bodyOut');

		if (_isRightMost)
		{
			var pw = parseInt(grid.parentNode.style.width);
			if ((isNaN(pw)) || (pw == 0))
				pw = grid.parentNode.clientWidth;
			if (pw == 0)
				pw = self.$(self.parent).clientWidth;

			var divider = 1;
			if (self.getParentObject().name.startsWith('F574018_10')) /* Obecne prilohy*/
				divider = 2;
			style.width = (_widths.mainWidth = pw - self.rect.left - 8) / divider;
		}
		else
		{
			style.position = 'absolute';
			style.width = _widths.mainWidth = self.rect.width;
		}
		self.$$('_nav').style.width = style.width;

		var newH;		
		if (_isBottomMost)
		{
			var ph = parseInt(grid.parentNode.style.height);
			if ((isNaN(ph)) || (ph == 0))
				ph = grid.parentNode.clientHeight;
			if (ph == 0)
				ph = self.$(self.parent).clientHeight;
			newH = ph - self.rect.top - 20; // - 50;
		}
		else
		{
			newH =  self.rect.height - 5;
			style.position = 'absolute';
		}

		style.height = newH;
		bodyOut.style.height = Math.max(newH - Math.max(self.$$('_nav').offsetHeight, 20) - Math.max(hdrOut.offsetHeight, 20) - 24, 40);
	}

	function _getColumnText(colName, row)
	{
		var cnt = "",
			pad = 0,
			ds = _ds,
			recs = ds.records,
			rec = recs[row];

		if (colName == '__Tree')
		{
			for (var k = 0; k < self.tvFields.length; k++)
				cnt += ds.getText(self.tvFields[k], row) + ", ";

			cnt = cnt.substr(0, cnt.length - 2);
			pad = 5 + rec.tvLevel * 10;

			if (rec.tvFlags == tvetCollapsed)
			{
				cnt = ('<span id="treeNode_' + row + '" class="gridCellTreeNode"><img id="treeNodeImg_' + row + '" class="gridCellTreeNodeImg" src="' + self.owner.images["folder-closed"].src + '" align="texttop" />' + cnt + '</span>');
			}
			else
			if (rec.tvFlags == tvetExpanded)
			{
				cnt = ('<span id="treeNode_' + row + '" class="gridCellTreeNode"><img id="treeNodeImg_' + row + '" class="gridCellTreeNode" src="' + self.owner.images["folder-open"].src + '" align="texttop" />' + cnt + '</span>');
			}
			else
			{
				pad += 19;
			}

		}
		else
		{
			cnt = ds.getText(colName, row);
			cnt = cnt == '' ? '&nbsp;' : cnt.escapeHtmlTags();
		}

		return {content: cnt, padding: pad};
	}

	function _fieldMenuClick(item, idx)
	{
	    if (_fieldMenuField == null)
	        return;

//	  alert('ok: ' + idx);
		if (idx == 0)
		{
			var qfd = new JQuickFilterDialog(self.owner, self.name, self.name + '_qFilter', self.dataset, _fieldMenuField, self.owner.localize("dlgQuickFilter"), '0;0;280;130');
			self.owner.registerObject(self.name + '_qFilter', qfd);
			self.owner.refresh(idMain);
		}
	}

	function _dsInfoChaged(records, pageSize, page)
	{
		var pgsel = self.owner.getObject('epk_pgsel_' + self.name); 
		if (pgsel != null)
		{
			pgsel.dispose();
			self.removeControl(pgsel);
			self.owner.unregisterObject(pgsel);
		}
		_renderPageSelector();
	}

	function _recordAdded(recNo, record)
	{
		var elem, newRow;
		var rowStart = self.name + '_row_';
		var colStart = self.name + '_cell_';
		var recCount = _ds.getRecCount();
		var cols = self.columns;
		var currElem = self.$(self.name + '_row_' + recNo);

		if (currElem != null)
		{
			for (var i = recCount - 2; i >= recNo; i--)
			{
				newRow = (i + 1).toString();
				elem = self.$(rowStart + i);
				elem.id = rowStart + newRow;
				elem.setAttribute('_row', newRow);

				for (var j = 0; j < cols.length; j++)
				{
					elem = self.$(colStart + i + '_' + j);
					elem.id = colStart + newRow + '_' + j;
				}

			}
		}

		var fs = new FastString();
		var tempDiv = self.owner.getTempDiv();
		_renderRow(fs, recNo, self.columns);
		tempDiv.innerHTML = fs.toString();

		var newElem = self.$(self.name + '_row_' + recNo);

		if (currElem != null)
		{
			currElem.parentNode.insertBefore(newElem, currElem);
		}
		else
			self.$(self.name + '_bodyIn').appendChild(newElem);

		if (self.activeRow != null)
		{
			if (self.activeRow >= parseInt(recNo))
				_unselectRow(self.activeRow + 1);
			else
				_unselectRow(self.activeRow);
		}

		self.activeRow = null;
		_selectActiveRow(recNo);
	} 

	function _recordChanged(recNo, fieldName, orgValue, value)
	{
		if (arguments.length == 0)
		{
			self.load();
			return;
		}

		if ((self.editRow == recNo) && (fieldName) && (self.edits[fieldName] != null))
			return;

		if (fieldName)
		{ 
			var i = self.columns.indexOf(fieldName);
			if (i < 0)
			  return;
	
			var elem = self.$(self.name + '_cell_' + recNo + '_' + i);
			if (elem == null)
			  return;
	
			elem.innerHTML = _ds.getText(fieldName, recNo);
		}
		else
		{
			self.columns.forEach(function (column, i)
			{
				if (self.edits[column] != null)
					return;

				var elem = self.$(self.name + '_cell_' + recNo + '_' + i);
				if (elem == null)
				  return;

				elem.innerHTML = _getColumnText(column, recNo).content;
			}, self);
		}
	}

	function _positionChanged()
	{
		var recNo = _ds.getRecNo();
		if (self.activeRow != recNo)
			_selectActiveRow(recNo);
	}

	function _recordsCleared()
	{
		if (self.editRow == null)
			self.toggle(false);

		self.$(self.name + '_bodyIn').innerHtml = '';
		_selector.clear();
		self.activeRow = null;
		self.editRow = null;
	}

	function _recordDeleted(recNo, record)
	{
		if (self.editRow == recNo)
			_removeInlineEdits();

		var elem = self.$(self.name + '_row_' + recNo);
		elem.parentNode.removeChild(elem);

		var elem, newRow;
		var rowStart = self.name + '_row_';
		var colStart = self.name + '_cell_';
		var recCount = _ds.getRecCount();
		var cols = self.columns;

		for (var i = recCount; i > recNo; i--)
		{
			newRow = (i - 1).toString();
			elem = self.$(rowStart + i);
			elem.id = rowStart + newRow;
			elem.setAttribute('_row', newRow);

			for (var j = 0; j < cols.length; j++)
			{
				elem = self.$(colStart + i + '_' + j);
				elem.id = colStart + newRow + '_' + j;
			}

		}

		self.activeRow = null;
		_selectActiveRow(recNo);
	}

	function _findParentRow(elem)
	{
		while ((elem != null) && ((elem.className.startsWith('gridCell')) || (elem.className == 'gridRowDivider')))
			elem = elem.parentNode;

		return elem;
	}

	function _keyDown(args)
	{
		if (self.getForm() != self.owner.activeForm)
			return;
	
		if (args == null)
			args = window.event;

		switch (args.keyCode)
		{
			case 37:	// left
				//self.dataset.movePrev();
				break;
			case 38:	// up
				self.dataset.movePrev();
				(args.preventDefault) ? args.preventDefault() : args.returnValue = false;
				break;
			case 39:	// right
				//self.dataset.movePrev();
				break;
			case 40:	// down
				self.dataset.moveNext();
				(args.preventDefault) ? args.preventDefault() : args.returnValue = false;
				break;
			case 27:	// esc
				self.toggle(false, true);
				(args.preventDefault) ? args.preventDefault() : args.returnValue = false;
				break;
			case 13:	// enter
				if (self.editRow != null){
					var rec = _ds.getCurrentRecord();
					if (rec.getState() == 1) {
						var obj = document.activeElement && self.owner.getObject(document.activeElement.id);
						if (obj && ((obj instanceof JEdit) || (obj instanceof JCalendar) || (obj instanceof JEnumPicker) || (obj instanceof JSetPicker))){
							if (obj.nextEdit) {
								var tb = self.$(obj.nextEdit);
								try
								{
									tb.focus();
									utils.selectText(tb);
								}
								catch(ex)
								{}
							} else {
								if (self.toggle(false)) {
									var datReadOnly = utils.inSet(dfReadOnly, _ds.flags);
									var rowReadOnly = false;
								
									if (!datReadOnly) {
								
										self.toggle(false);
										var query = new Postback(raOperateDataset);

										query.set("afc", self.name);
										_ds.insertRecord(query);
										self.toggle(true);
									}
								}
							}
						}
					} else {
						// fix: input calls onChange event after loosing his focus
						if (document.activeElement && document.activeElement.blur){
							document.activeElement.blur();
						}
						self.toggle();
					}
				} else {
					// is F4?
					if (utils.coalesce(self.flags, "").indexOf("remote") == -1){
						self.toggle(true);
					} else {
						if (self.activeRow){
							var row = self.$(self.name + '_row_' + self.activeRow);
							if ((row != null) && (row.className.indexOf('gridRowActive')))
								_choose({target:row});
						}
					}
				}
				(args.preventDefault) ? args.preventDefault() : args.returnValue = false;
				break;
			case 45: //insert
			
				var datReadOnly = utils.inSet(dfReadOnly, _ds.flags);
				var rowReadOnly = false;
		    
				if (!datReadOnly) {
			
					self.toggle(false);
					var query = new Postback(raOperateDataset);

					query.set("afc", self.name);
					_ds.insertRecord(query);
					self.toggle(true);
					(args.preventDefault) ? args.preventDefault() : args.returnValue = false;
				}
				break;
			case 115: //F4
				if (self.editRow != null){
					var obj = document.activeElement && self.owner.getObject(document.activeElement.id);
					if (obj && (obj instanceof JEdit) && obj.type == etRemote){
						obj.click();
						document.activeElement.blur();
					}
					(args.preventDefault) ? args.preventDefault() : args.returnValue = false;
				}
				break;
		}

//		alert(event.keyCode);
	}

	function _mouseClick(args)
	{
		if (_form != null)
			_form.setActiveControl(self);

		var toggleNode = false;
		var elem = utils.getEventElement(args);

		var toggleNode =  (elem.className.startsWith('gridCellTreeNode')) ? true : false;

		var rowElem = _findParentRow(elem);

		if (rowElem.className.startsWith('gridRow'))
		{
			var rowNr = parseInt(rowElem.getAttribute('_row'));
			if (self.editRow == rowNr)
			  return;

			_selectActiveRow(rowNr, args);
		}

		if (elem.id.startsWith(self.name + '_fieldArrow'))
		{
			var col = parseInt(elem.getAttribute('_col'));
			_fieldMenuField = self.dataset.fields[self.columns[col]];
			_fieldMenu.showMenu(args, elem);
		}

		if (toggleNode)
			_ds.toggleNode();
	}

	function _mouseDown(args)
	{
		var elem = utils.getEventElement(args);

		if (elem.className == 'gridHeaderDivider')
		{
			_resizing.pos = utils.getMouseXY(args);
			_resizing.elem = elem.previousSibling;
			_resizing.width = parseInt(_resizing.elem.clientWidth);
			_resizing.totalWidth = parseInt(self.$(self.name+ '_headerIn').clientWidth);
			_resizing.isResizing = true;

			if (self.editRow != null)
			{
				var col = parseInt(_resizing.elem.getAttribute('_col'));
				var edit = self.$(self.name + "_e_" + col);
				if (edit != null)
					_resizing.editWidth = parseInt(edit.clientWidth);
			}

			var prnt = _resizing.elem.parentNode;
			prnt.onmousemove = _mouseMove;
			prnt.onmouseup = _mouseUp;
			prnt.style.cursor = 'pointer';
		}
	}

	function _mouseMove(args)
	{
		if (_resizing.isResizing)
		{
			var pos = utils.getMouseXY(args);
			var min = Math.min(_resizing.width, grdMinWidth);
			var newSize = Math.max(_resizing.width - (_resizing.pos.x - pos.x), min);
			var currSize = parseInt(_resizing.elem.style.width);
			if (currSize == newSize)
				return;

			_resizing.elem.style.width = newSize;

			var col = parseInt(_resizing.elem.getAttribute('_col'));
			_widths.widths[col] = newSize;

			var recCount = _ds.getRecCount();
			for (var i = 0; i < recCount; i++)
			{
				var elem = self.$(self.name + '_cell_' + i + '_' + col);
				elem.style.width = newSize;
				if (!browser.isIE)
				{
					var pad = utils.toInt(elem.style.paddingLeft);
					elem.style.width = newSize - pad;
				}

				if (elem._isEditing)
				{
					var edit = self.$(self.name + "_e_" + col);
					if (edit != null)
						edit.style.width = _resizing.editWidth - _resizing.width + newSize;
				}
			}

			var gridWidth = _resizing.totalWidth - _resizing.width + newSize;
			self.$(self.name+ '_headerIn').style.width  = gridWidth;
			self.$(self.name+ '_bodyIn').style.width    = gridWidth;
			_widths.totalWidth = gridWidth;
		}
	}

//	var _styleCellRO = utils.getStyleRule('.readOnly', 0, owner.getDocument());

	function _setCellStyles(rowElem)
	{
		var rowNr = parseInt(rowElem.getAttribute('_row'));
		var isActiveRow = self.activeRow == rowNr;
		var isOver = rowElem.className.indexOf('gridRowOver') > -1;
		var isDSReadOnly = _ds.isReadOnly(); 

//		var color = _styleCellRO['backgroundColor'];

		var cells = rowElem.getElementsByTagName('DIV');

		var i = cells.length;
		if (i > 0)
		{
			do
			{
				var cell = cells[i - 1];
				if ((!cell.className.startsWith('gridCell')) || (cell.className == 'gridCellTree'))
					continue;

				if (cell.getAttribute('_isEditing') == 'true')
					continue;
	
				var cName = 'gridCell';

				if (/*(!isActiveRow) && */ (!isOver) && (!_isRowSelected(rowNr)))
				{
					if ((cell.getAttribute('_readOnly') == 'true') && (!isDSReadOnly))
					{
						cName += ' readOnly';
					}
					else
					if (cell.getAttribute('_required') == 'true')
					{
						cName += ' requiredField';
					}
				}
	
				if (cell.className != cName)
				{
					cell.className = cName;
				}
			}
			while (--i);
		}

	} 

	function _mouseOver(args)
	{
		var elem = utils.getEventElement(args);

		var rowElem = _findParentRow(elem);

		if ((rowElem != null) && (rowElem.className.startsWith('gridRow')))
		{
			var rowNr = parseInt(rowElem.getAttribute('_row'));
			if (self.editRow == rowNr)
			  return;

			var cName = 'gridRowOver';
			if (self.activeRow == rowNr)
				cName += ' gridRowActive';
			rowElem.className = cName;

			_setCellStyles(rowElem);
		}

		while (elem.className.startsWith('gridCellTree'))
			elem = elem.parentNode;

		if (elem.className.startsWith('gridCell'))
		{
				elem.className = 'gridCellOver';
		}
	}
	
	function _mouseOut(args)
	{
		var elem = utils.getEventElement(args);

		if ((elem.className == 'gridHeaderIn') && (_resizing.isResizing))
		{
			_resizing.isResizing = false;
			return;
		}

		var rowElem = _findParentRow(elem);

		if ((rowElem != null) && (rowElem.className.startsWith('gridRow')))
		{
			var rowNr = parseInt(rowElem.getAttribute('_row'));

			var cName = 'gridRowSelected';

			if (!_isRowSelected(rowNr))
				cName = (rowNr % 2) ? 'gridRow gridRowOdd' : 'gridRow';

			if (self.activeRow == rowNr)
				cName += ' gridRowActive';

			rowElem.className = cName;

			_setCellStyles(rowElem);
		}

	}
	
	function UpdateClientGrid(){
		var query  = new Postback();
		query.set("afm", self.owner.activeForm.name);
		query.set("obj", self.name);
		query.set("columns_width", _widths.widths.toString());

		self.owner.post(raUpdateGrid, query);
	
	}

	function _mouseUp(args)
	{
	  if (_resizing.isResizing)
	  {
		UpdateClientGrid();
	    _resizing.isResizing = false;
	    var prnt = _resizing.elem.parentNode;
        prnt.onmousemove = null;
        prnt.onmouseup = null;
        prnt.style.cursor = ''; 
	  }
	}	

	function onScroll()
	{
	}

	function click()
	{
	}

	function mark()
	{
	}

	function highlight()
	{
	}

	function _choose(args)
	{
		var elem = utils.getEventElement(args);
		if ((elem.className.startsWith('gridCell')) || (elem.className == 'gridRowDivider'))
		  elem = elem.parentNode;

		if (elem.className.startsWith('gridRow'))
		{
			if (utils.coalesce(self.flags, "").indexOf("remote") == -1)
			{
				if (self.editRow == null)
					self.toggle();
				return;
			}

			var row = parseInt(elem.getAttribute('_row'));

			var body   = self.$(self.name + "_body");
			var query  = new Postback();
			var pfmobj = self.owner.getObject(self.owner.getObject(_ds.parent).previous);

			var prevDS = pfmobj.dataset;
			if (pfmobj.activeControl != null)
			{
				prevDS = pfmobj.activeControl.dataset;
			}

			query.set("pfm", pfmobj.name);
			query.set("pds", prevDS.name);

			var rec = _ds.getCurrentRecord().key;
/*
			if ((utils.coalesce(self.getForm().flags, "").indexOf("multi") > -1))
			{
				var selRecs = _selector.toString();
				if (_selector.contains(rec))
					rec = selRecs;
				else
				if (! utils.isEmpty(selRecs))
					rec += (scSafeSep + selRecs);  
			}
*/
			query.set("rec", rec);

			self.owner.post(raRemoteSelect, query);
		}
	}

	function setActiveRow(rowNr, doSelect, multi)
	{
		if ((_ds.getRecCount() <= rowNr) || ((self.activeRow == rowNr) && (_ds.getRecNo() == rowNr)))
			return;

		if (self.activeRow > _ds.getRecCount())
			rowNr = _ds.getRecCount();
		else
		if (self.activeRow != null)
			_saveRow(self.activeRow);

		if (!utils.isEmpty(self.activeRow))
		{
			var cae = self.$(self.name + '_row_' + self.activeRow);
			if ((cae != null) && (cae.className.indexOf('gridRowActive')))
			{
				cae.className = cae.className.replace('gridRowActive', '');
			}
		}

		self.activeRow = rowNr;
		_ds.gotoRow(rowNr);

		var rec = _ds.getCurrentRecord();
		_savedRec = null;
		if (rec != null)
			_savedRec = rec.getValues();

		var elem = self.$(self.name + '_row_' + rowNr);
		if (elem != null)
		{
			elem.className += ' gridRowActive';

			var bodyOut = self.$$('_bodyOut');
			if ((elem.offsetTop > (bodyOut.clientHeight + bodyOut.scrollTop - 5)) || (elem.offsetTop < bodyOut.scrollTop))
				elem.scrollIntoView();
			
		}
	}
	
	function _isRowSelected(row)
	{
		return _selector.contains(_ds.getRecord(row).key);
	}

	function _unselectRow(row)
	{
		var elem = self.$(self.name + '_row_' + row);
		elem.className = (row % 2) ? 'gridRow gridRowOdd' : 'gridRow';
	}

	function _unselectAll()
	{
		_selector.getKeys().forEach(function (item)
		{
			_unselectRow(_ds.findByKey(item));
		}, self);
	}

	function _selectActiveRow(rowNr, mouseArgs)
	{
		var editing = false;
		if (!utils.isEmpty(self.editRow))
		{
			editing = true;
			if (! self.toggle(false))
				return;
		}

		var multi = utils.getCtrlKey(mouseArgs) && self.multiSelect;

		self.selectRow(rowNr, multi);
		self.setActiveRow(rowNr);

		if (editing)
		  self.toggle(true);
	}

	function selectRow(row, multi)
	{
		var elem = self.$(self.name + '_row_' + row);

		if (elem == null)
			return;

		if (multi)
		{
			var key = _ds.getRecord(row).key;
			if (_selector.contains(key))
			{
				_unselectRow(row);
				_selector.remove(key);
			}
			else
			{
				_selector.add(key);
				if (elem.className != 'gridRowOver')
					elem.className = 'gridRowSelected';
			}
		}

	} 

	function toggle(editable, cancel)
	{
		if (editable == null)
			editable = (utils.isEmpty(self.editRow)) ? true : false;
		else
		if ((editable && !utils.isEmpty(self.editRow)) || (!editable && utils.isEmpty(self.editRow)))
			return true;

		if (editable)
		{
			if ((self.activeRow == null) || (_ds.isReadOnly(null, self.activeRow)))
				return true;

			self.editRow = self.activeRow;

			var rect = '0; 0; 100%; 100%';
			var firstEdit = null;
			var tabIndex = 20000;

			var cols = self.columns;
			var colsLen = cols.length;
			var prevEdit = null;
			for (var i = 0; i < colsLen; i++)
			{
				if (self.isComputedColumn(i))
					continue;

				var col = cols[i];
				var field = _ds.fields[col];

				var rec = _ds.getCurrentRecord();
				//if (!utils.inSet(ffReadOnly, field.flags))
				if ((!rec.isReadOnly(col)) && (!_ds.isReadOnly()))
				{
					var editName = self.name + "_e_" + i;
					if (!firstEdit)
						firstEdit = editName;
					var cell = self.$(self.name + '_cell_' + self.editRow + '_' + i);

					var obj = null;
					if (utils.inList(field.type, ftString, ftInteger, ftFloat))
					{
						obj = new JEdit(self.owner, self.name, editName, _ds, field.name, rect, utils.inSet(ffRemote, field.flags) ? etRemote : etSimple, null, true, tabIndex);
					}
					if (utils.inList(field.type, ftDate, ftTime, ftDateTime))
					{
						obj = new JCalendar(self.owner, self.name, editName, _ds, field.name, rect, utils.inSet(ffRemote, field.flags) ? etRemote : etSimple, null, true, tabIndex);
					}
					if (field.type == ftEnum)
					{
						obj = new JEnumPicker(self.owner, self.name, editName, _ds, field.name, rect, null, true, tabIndex);
					}
					if (field.type == ftSet)
					{
						obj = new JSetPicker(self.owner, self.name, editName, _ds, field.name, rect, null, true, tabIndex);
					}

					if (obj != null)
					{
						if (prevEdit != null){
							prevEdit.nextEdit = editName;
						}
						prevEdit = obj;
						self.edits[field.name] = obj;
						self.owner.registerObject(editName, obj);
						self.addControl(obj);

						cell.setAttribute('_isEditing', 'true');

						var fs = new FastString();
						obj.inplace = true;
						obj.render(fs);
						cell.className += ' gridCellEdit';
						cell.innerHTML = fs.toString();

						if (utils.inSet(cmLoad, obj.methods))
							obj.load();

						cell.className = 'gridCellEdit';		// we must set it after obj.load() otherwise we get there height of zero
						tabIndex++;
					}
				}
			}
			if (firstEdit)
			{
				var tb = self.$(firstEdit);
				try
				{
					tb.focus();
					utils.selectText(tb);
				}
				catch(ex)
				{}
			}
			
		}
		else
		{
			if (utils.isEmpty(self.editRow))
				return;

			if (cancel)
			{
				for (var i = 0; i < self.columns.length; i++)
				{
					var field = _ds.fields[self.columns[i]];
					_ds.set(field.name, _savedRec[field.name]);
				}
			}

			_saveRow(self.activeRow);
			if ((self.editRow != null) && (_ds.getRecord(self.editRow) != null) && ((_ds.getRecord(self.editRow).getState() == rsModified) || (_ds.getRecord(self.editRow).getState() == rsAdded)))
				return false;

			_removeInlineEdits();
		}

		return true;
	}

	function _removeInlineEdits()
	{
		var cols = self.columns;
		var colsLen = cols.length;
		for (var i = 0; i < colsLen; i++)
		{
			if (isComputedColumn(i))
				continue;

			var field = _ds.fields[cols[i]];
			var editName = self.name + "_e_" + i;
			var elem = self.$(editName);
			var edit = self.edits[field.name];

			if (edit == null)
			  continue;

			if (edit.dispose)
			  edit.dispose();
			self.removeControl(edit);
			self.owner.unregisterObject(edit.name);

			if (elem != null)
			{
				var prnt = elem.parentNode;
				while ((prnt != null) && (prnt.id == ''))
					prnt = prnt.parentNode;

				if (prnt != null)
				{
					var content = _ds.getText(cols[i]);
					prnt.removeAttribute('_isEditing'); 
					prnt.innerHTML = content;
					prnt.className = 'gridCell';
					prnt.style.textAlign = elem.style.textAlign;
				}
				delete elem;
			}

			delete self.edits[field.name];
		}

		self.editRow = null;
		
		if (_form != null){
			if (self.tab){
				if (self.getParentObject().active && (self.getParentObject().active == self.tab))
					_form.setActiveControl(self);
			} else {
				_form.setActiveControl(self);
			}
			
		}
	}

	function _saveRow(row)
	{
		var rec = _ds.getRecord(row);
		if (rec == null)
		        return;

		if (utils.inList(rec.getState(), rsModified, rsAdded))
		{
			if (self.owner.askForSaving(self))
			{
				_ds.saveRecord(row);
			}
			else
			if (rec.getOriginalState() == rsAdded)
			{
				_ds.deleteRecord(row);
			}
			else
			{
				rec.setValues(_savedRec);
			}
		}
	}

	function isComputedColumn(col) 
	{
		if (self.columns[col] == '__Tree')
		  return true;
		
		return false;
	}

	function _navIconClick(args)
	{
		if (self.editRow == null)
			self.toggle(false);

		var id = utils.getEventElement(args).id;
		var pg = null;
		
		if (utils.inList(id, "fp", "pp", "np", "lp", "ps"))
		{
		
		  /* Switch page */
		  switch (id)
		  {
		    case "fp" :
			    self.navigate(0);
		        break;
		
		    case "pp" : 
			    self.navigate(-1, true);
			    break;
		
		    case "np" :
			    self.navigate(1, true);
			    break;
		
		    case "lp" :
			    self.navigate(maxInt);
		        break;
		
		    case "ps" :
		      pg = null;
		      var picker = self.$('epk_pgsel_' + self.name);
		      if (picker != null)
		        self.navigate(parseInt(picker.value) - 1);
		      break;
		  }
		}
		else
		if (utils.inList(id, "er", self.name + "_ir", self.name + "_irl", "dr"))
		{
		  var datReadOnly = utils.inSet(dfReadOnly, _ds.flags);
		  var rowReadOnly = false;
		
		  if (utils.inList(id, "er", "dr") && (_ds.getRecCount() == 0))
		    return; 
		    
		  rowReadOnly = utils.inList(id, "er", "dr") ? utils.inSet(rfReadOnly, _ds.getCurrentRecord().flags) : false;

		  if (!(datReadOnly || rowReadOnly))
		  {
		  	switch (id)
		  	{
		  	case self.name + "_ir":
		  	case self.name + "_irl":
		  		self.toggle(false);
		  		var query = new Postback(raOperateDataset);

		  		if (id == self.name + "_irl")
		  			query.set("level", "true");

		  		query.set("afc", self.name);
		  		_ds.insertRecord(query);
		  		self.toggle(true);
		  		break;

		  	case "er":
		  		self.toggle();
		  		break;

		  	case "dr":
		  		self.toggle(false);
		  		_ds.deleteRecord();
		  		break;
		  	}
		  }
		}

	}

	function navigate(page, relative)
	{
		var pg = parseInt(_ds.pageNo);
		var pageCount = Math.floor(_ds.getRealRecCount() / _ds.pageSize);

		if (relative)
		{
			if ((pg + page < 0) || (pg + page > pageCount))
				return;

			page = pg + page;
		}
		else
		{
			if ((page < 0)) //|| (page > pageCount))
				return;
		}

		if (isNaN(pageCount))
			return;

		if (pg != null) 
		{
			_ds.refreshData(page);
		}
	}

	function dispose()
	{
		if (_disposed)
			return;

		var grid = self.$(self.name);
		var hdrOut = self.$(self.name+ '_headerOut');
		var hdrIn = self.$(self.name+ '_headerIn');
		var bodyOut = self.$(self.name+ '_bodyOut');
		var bodyIn = self.$(self.name+ '_bodyIn');

		var nav = self.$(self.name + '_nav');
		if (nav != null)  
			nav.onclick = null;

		if (bodyOut != null)  
			bodyOut.onscroll = null;

		if (grid != null)
		{
			grid.onmousedown = null;
			grid.onmouseover = null;    
			grid.onmouseout  = null;
			grid.onclick     = null;
			grid.onkeydown   = null;
		}

		_form = null;
		_savedRec = null;
		self.edits = null;
		self.pagingHelper = null;

		self.disposeChildren();
		self.base.dispose.call(self);
		_disposed = true;
	}
}

JGrid.inheritsFrom(ControlBase);

if (typeof(loadNextScript) != 'undefined')
	loadNextScript();